﻿//-----------------------------------------------------------------------
// <copyright file="IntrospectionRule.cs">
//     Copyright (c) Nicole Calinoiu. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;

using Microsoft.FxCop.Sdk;
using Microsoft.VisualStudio.CodeAnalysis.Extensibility;

namespace Bordecal.FxCop.Sdk
{
	/// <summary>
	/// Base class for introspection rules.
	/// </summary>
	public abstract class IntrospectionRule : BinaryReadOnlyVisitor, IIntrospectionRule, IRule
	{
		#region Fields

		private readonly IRuleDescriptor _descriptor;

		private readonly ProblemCollection _problems;

		private static readonly MethodInfo _argumentFormatter = typeof(BaseIntrospectionRule)
			.Assembly
			.GetType("Microsoft.FxCop.Sdk.ResolutionFormatter", true, false)
			.GetMethod(
				"FormatArguments",
				BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
				null,
				new Type[] { typeof(object[]) },
				null);

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="IntrospectionRule"/> class.
		/// </summary>
		/// <param name="descriptor">The <see cref="IRuleDescriptor"/> for the rule.</param>
		protected IntrospectionRule(IRuleDescriptor descriptor)
		{
			if (descriptor == null)
			{
				throw new ArgumentNullException("descriptor");
			}

			this._descriptor = descriptor;

			this._problems = new ProblemCollection(this);
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the <see cref="IRuleDescriptor"/> for the rule.
		/// </summary>
		protected IRuleDescriptor Descriptor
		{
			get
			{
				return this._descriptor;
			}
		}

		/// <summary>
		/// Gets the category under which the rule should appear in reports and the FxCop user interface.
		/// </summary>
		public string Category
		{
			get
			{
				return this.Descriptor.Category;
			}
		}

		/// <summary>
		/// Gets the certainty that a reported problem is an actual violation of the rule.
		/// </summary>
		public int Certainty
		{
			get
			{
				return this.Descriptor.Certainty;
			}
		}

		/// <summary>
		/// Gets the unique identifier for the rule.
		/// </summary>
		public string CheckId
		{
			get
			{
				return this.Descriptor.CheckId;
			}
		}

		/// <summary>
		/// Gets a description of the rule.
		/// </summary>
		public string Description
		{
			get
			{
				return this.Descriptor.Description;
			}
		}

		/// <summary>
		/// Gets the e-mail address to be used
		/// for support for the rule.
		/// </summary>
		public string Email
		{
			get
			{
				return this.Descriptor.Email;
			}
		}

		/// <summary>
		/// Gets the <see cref="FixCategories"/> for violations of the rule.
		/// </summary>
		public FixCategories FixCategories
		{
			get
			{
				return this.Descriptor.FixCategories;
			}
		}

		/// <summary>
		/// Gets the <see cref="MessageLevel"/> for violations of the rule.
		/// </summary>
		public MessageLevel MessageLevel
		{
			get
			{
				return this.Descriptor.MessageLevel;
			}
		}

		/// <summary>
		/// Gets the name of the rule.
		/// </summary>
		public string Name
		{
			get
			{
				return this.Descriptor.Name;
			}
		}

		/// <summary>
		/// Gets the name of the rule owner.
		/// </summary>
		public string Owner
		{
			get
			{
				return this.Descriptor.Owner;
			}
		}

		/// <summary>
		/// Gets the help URL for the rule.
		/// </summary>
		public string Url
		{
			get
			{
				return this.Descriptor.Url;
			}
		}

		/// <summary>
		/// Gets the collection of problems found by the rule.
		/// </summary>
		public ProblemCollection Problems
		{
			get
			{
				return this._problems;
			}
		}

		/// <summary>
		/// Gets the <see cref="TargetVisibilities"/> against which the rule will be evaluated.
		/// </summary>
		public virtual TargetVisibilities TargetVisibility
		{
			get
			{
				return TargetVisibilities.All;
			}
		}

		#endregion

		#region Methods

		/// <summary>
		/// Performs pre-analysis setup tasks.
		/// </summary>
		/// <remarks>
		/// Derived classes should override this method to add their own setup tasks, if necessary.
		/// </remarks>
		public virtual void BeforeAnalysis()
		{
		}

		/// <summary>
		/// Performs post-analysis clean-up tasks.
		/// </summary>
		/// <remarks>
		/// Derived classes should override this method to add their own clean-up tasks, if necessary.
		/// </remarks>
		public virtual void AfterAnalysis()
		{
		}

		/// <summary>
		/// When overridden in a subclass, verifies compliance of a method parameter
		/// with the implemented rule.
		/// </summary>
		/// <param name="parameter">The target parameter.</param>
		/// <returns>A collection of found problems.</returns>
		public virtual ProblemCollection Check(Parameter parameter)
		{
			return null;
		}

		/// <summary>
		/// When overridden in a subclass, verifies compliance of a type member
		/// with the implemented rule.
		/// </summary>
		/// <param name="member">The target member.</param>
		/// <returns>A collection of found problems.</returns>
		public virtual ProblemCollection Check(Member member)
		{
			return null;
		}

		/// <summary>
		/// When overridden in a subclass, verifies compliance of a type
		/// with the implemented rule.
		/// </summary>
		/// <param name="type">The target type.</param>
		/// <returns>A collection of found problems.</returns>
		public virtual ProblemCollection Check(TypeNode type)
		{
			return null;
		}

		/// <summary>
		/// When overridden in a subclass, verifies compliance of a resource
		/// with the implemented rule.
		/// </summary>
		/// <param name="resource">The target resource.</param>
		/// <returns>A collection of found problems.</returns>
		public virtual ProblemCollection Check(Resource resource)
		{
			return null;
		}

		/// <summary>
		/// When overridden in a subclass, verifies compliance of a module
		/// with the implemented rule.
		/// </summary>
		/// <param name="module">The target module.</param>
		/// <returns>A collection of found problems.</returns>
		public virtual ProblemCollection Check(ModuleNode module)
		{
			return null;
		}

		/// <summary>
		/// When overridden in a subclass, verifies compliance of a namespace
		/// with the implemented rule.
		/// </summary>
		/// <param name="namespaceName">The target namespace.</param>
		/// <param name="types">The types contained by the namespace.</param>
		/// <returns>A collection of found problems.</returns>
		public virtual ProblemCollection Check(string namespaceName, TypeNodeCollection types)
		{
			return null;
		}

		/// <summary>
		/// Creates a <see cref="Resolution"/> using the default resolution pattern string.
		/// </summary>
		/// <param name="arguments">The arguments with which the populate the resolution pattern string.</param>
		/// <returns>The created <see cref="Resolution"/>.</returns>
		protected Resolution GetResolution(params object[] arguments)
		{
			return this.GetNamedResolution(null, arguments);
		}

		/// <summary>
		/// Creates a <see cref="Resolution"/> using a specific resolution pattern string.
		/// </summary>
		/// <param name="name">The resolution pattern string name.</param>
		/// <param name="arguments">The arguments with which the populate the resolution pattern string.</param>
		/// <returns>The created <see cref="Resolution"/>.</returns>
		protected Resolution GetNamedResolution(string name, params object[] arguments)
		{
			string[] formattedArguments = (string[])IntrospectionRule._argumentFormatter.Invoke(
				null,
				BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
				null,
				new object[] { arguments },
				CultureInfo.CurrentCulture);

			return new Resolution(name, this.Descriptor.GetResolutionText(name), formattedArguments);
		}

		/// <summary>
		/// Adds a new problem to the problem collection.
		/// </summary>
		/// <param name="node">The node in which the problem was found.</param>
		/// <param name="resolutionParameters">The arguments with which to populate the resolution format string.</param>
		protected void AddProblem(Node node, params object[] resolutionParameters)
		{
			this.AddProblem(node, null, this.FixCategories, resolutionParameters);
		}

		/// <summary>
		/// Adds a new problem to the problem collection.
		/// </summary>
		/// <param name="resolutionName">The resolution name.</param>
		/// <param name="node">The node in which the problem was found.</param>
		/// <param name="resolutionParameters">The arguments with which to populate the resolution format string.</param>
		protected void AddProblem(string resolutionName, Node node, params object[] resolutionParameters)
		{
			this.AddProblem(resolutionName, node, null, this.FixCategories, resolutionParameters);
		}

		/// <summary>
		/// Adds a new problem to the problem collection.
		/// </summary>
		/// <param name="node">The node in which the problem was found.</param>
		/// <param name="id">The identifier for the problem.</param>
		/// <param name="resolutionParameters">The arguments with which to populate the resolution format string.</param>
		protected void AddIdentifiedProblem(Node node, string id, params object[] resolutionParameters)
		{
			this.AddProblem(node, id, this.FixCategories, resolutionParameters);
		}

		/// <summary>
		/// Adds a new problem to the problem collection.
		/// </summary>
		/// <param name="node">The node in which the problem was found.</param>
		/// <param name="id">The identifier for the problem.</param>
		/// <param name="fixCategory">The <see cref="FixCategories"/> for the resolution.</param>
		/// <param name="resolutionParameters">The arguments with which to populate the resolution format string.</param>
		protected void AddProblem(Node node, string id, FixCategories fixCategory, params object[] resolutionParameters)
		{
			this.AddProblem(null, node, id, fixCategory, resolutionParameters);
		}

		/// <summary>
		/// Adds a new problem to the problem collection.
		/// </summary>
		/// <param name="resolutionName">The resolution name.</param>
		/// <param name="node">The node in which the problem was found.</param>
		/// <param name="id">The identifier for the problem.</param>
		/// <param name="fixCategory">The <see cref="FixCategories"/> for the resolution.</param>
		/// <param name="resolutionParameters">The arguments with which to populate the resolution format string.</param>
		protected void AddProblem(string resolutionName, Node node, string id, FixCategories fixCategory, params object[] resolutionParameters)
		{
			Problem problem = new Problem(this.GetNamedResolution(resolutionName, resolutionParameters), node);
			problem.Id = id ?? string.Empty;
			problem.FixCategory = fixCategory;

			this.Problems.Add(problem);
		}
		
		#endregion
	}
}